package Socket.Cenoura;

import Server.Prado;
import Common.Cenoura;
import Common.EstadoCenoura;
import java.util.Random;
import java.net.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import Socket.*;

public class CenouraSocket extends Cenoura implements Runnable{

    private Prado prado;
    private final String host;
    private final int port;
    private ObjectOutputStream objout;
    private ObjectInputStream objin;
    private Socket com;
    private Message saída;
    private Response entrada = null;
    Random genSeed = new Random();

    public CenouraSocket (Prado prado, int id, int Eg,int Ef,int Tf,int Et, int Smin, int Smax, int line, int column, String Host, int porto, int server_port) {
        super( id, 0, Eg, Ef, Tf, Smin, Smax, line, column);
        this.prado=prado;
        this.host = Host;
        this.port = porto;
        this.com = new Socket();
        try {
            com.connect(new InetSocketAddress (host, port));
        }
        catch (UnknownHostException e) {
            System.out.println ("Cenoura - o nome do sistema computacional onde reside o servidor é desconhecido: " + host);
            e.printStackTrace ();
            System.exit (1);
        }
        catch (NoRouteToHostException e) {
            System.out.println ("Cenoura - o sistema computacional onde reside o servidor é inatingível: " + host);
            e.printStackTrace ();
            System.exit (1);
        }
        catch (ConnectException e) {
            System.out.println ("Cenoura - o servidor não está activo em: " + host + "." + port);
            System.out.println (e.getMessage ());
            e.printStackTrace ();
            System.exit (1);
        }
        catch (SocketTimeoutException e) {
        System.out.println ("Cenoura - ocorreu um time out no estabelecimento da ligação a: " + host + "." + port);
        }
        catch (SocketException e) {
            System.out.println  ("Cenoura - fila de espera cheia no estabelecimento da ligação a: " + host + "." + port);
        }
        catch (IOException e) {
            System.out.println ("Cenoura - ocorreu um erro indeterminado no estabelecimento da ligação: " + host + "." + port);
            e.printStackTrace ();
            System.exit (1);
        }
    }

    private void growing_a_bit () {
        if((this.getEnergia() + this.getEg()) > this.getEf())
            this.setEnergia(this.getEf());
        else
            this.setEnergia(this.getEnergia() + this.getEg());
        saída = new Message(TypeFunction.informCenoura, 0, this.getId(), this.getEnergia(), this.getX(), this.getY(), 0, 0, null, EstadoCenoura.Growing);
        try {
            objout.writeObject((Object)saída);
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> growing_a_bit ()");
            System.exit(-1);
        }
    }

    private void germinating () {
        saída = new Message(TypeFunction.canGerminate, this.getId(), 0, 0, this.getX(), this.getY(), 0, 0, null, null);
        try {
            objout.writeObject((Object)saída);
            entrada = (Response)objin.readObject();
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating()");
            System.exit(-1);
        }
        catch(ClassNotFoundException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object Class Not Found --> germinating()");
            System.exit(1);
        }
        if(entrada.getbValor()) {
            this.setState(EstadoCenoura.Growing);
            saída = new Message(TypeFunction.informCenoura, 0, this.getId(), this.getEnergia(), this.getX(), this.getY(), 0, 0, null, EstadoCenoura.Growing);
            try {
                objout.writeObject((Object)saída);
            }
            catch(IOException ex) {
                System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> entrada.getbValor()");
                System.exit(-1);
            }
            super.log.Log("Mudanca de estado da cenoura " + this.getId() + ": " + this.getState());
        }
    }

    private void flowering () {
        this.setState(EstadoCenoura.Flower);
        saída = new Message(TypeFunction.informCenoura, 0, this.getId(), this.getEnergia(), this.getX(), this.getY(), 0, 0, null, EstadoCenoura.Flower);
        try {
            objout.writeObject((Object)saída);
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> flowering ()");
            System.exit(-1);
        }
        super.log.Log("Mudanca de estado da cenoura " + this.getId() + ": " + this.getState());
    }

    public int wait_for_pollen (int current_Tf) {
        return --current_Tf;
    }

    private void die () {
        saída = new Message(TypeFunction.hasSeeds, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
        try {
            objout.writeObject((Object)saída);
            entrada = (Response)objin.readObject();
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> die () --> TypeFunction.hasSeeds");
            System.exit(-1);
        }
        catch(ClassNotFoundException ex) {
            System.exit(1);
        }
        if(entrada.getbValor())
            this.setState(EstadoCenoura.Seed);
        else
            this.setState(EstadoCenoura.Dead);
        saída = new Message(TypeFunction.informCenoura, 0, this.getId(), this.getEnergia(), this.getX(), this.getY(), 0, 0, null, EstadoCenoura.Dead);
        try {
            objout.writeObject((Object)saída);
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> die () --> TypeFunction.informCenoura");
            System.exit(-1);
        }
        super.log.Log("Mudanca de estado da cenoura " + this.getId() + ": " + this.getState());
    }

    private int pollinization () {
        this.setState(EstadoCenoura.Seeding);
        saída = new Message(TypeFunction.informCenoura, 0, this.getId(), this.getEnergia(), this.getX(), this.getY(), 0, 0, null, EstadoCenoura.Seeding);
        try {
            objout.writeObject((Object)saída);
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> pollinization ()");
            System.exit(-1);
        }
        super.log.Log("Mudanca de estado da cenoura " + this.getId() + ": " + this.getState());
        return genSeed.nextInt(this.getSmax() + 1 - this.getSmin()) + this.getSmin();

    }

    private int seeding (int Nseeds) {
        Random chooser = new Random();
        int direction = chooser.nextInt(8);
        /*
         * direction:   0 1 2
         *              7   3
         *              6 5 4
         */
        switch(direction) {
            case 0: sendSeed(this.getX() - 1, this.getY() - 1); break;
            case 1: sendSeed(this.getX(), this.getY() - 1); break;
            case 2: sendSeed(this.getX() + 1, this.getY() - 1); break;
            case 3: sendSeed(this.getX() + 1, this.getY()); break;
            case 4: sendSeed(this.getX() + 1, this.getY() + 1); break;
            case 5: sendSeed(this.getX(), this.getY() + 1); break;
            case 6: sendSeed(this.getX() - 1, this.getY() + 1); break;
            case 7: sendSeed(this.getX() - 1, this.getY());
        }
        return Nseeds--;
    }

    private void die_by_oldness () {
        saída = new Message(TypeFunction.hasSeeds, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
        try {
            objout.writeObject((Object)saída);
            entrada = (Response)objin.readObject();
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> die_by_oldness () --> TypeFunction.hasSeeds");
            System.exit(-1);
        }
        catch(ClassNotFoundException ex) {
            System.exit(1);
        }
        if(entrada.getbValor())
            this.setState(EstadoCenoura.Seed);
        else
            this.setState(EstadoCenoura.Dead);
        saída = new Message(TypeFunction.informCenoura, 0, this.getId(), 0, this.getX(), this.getY(), 0, 0, null, EstadoCenoura.Dead);
        try {
            objout.writeObject((Object)saída);
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> die_by_oldness () --> TypeFunction.informCenoura");
            System.exit(-1);
        }
        super.log.Log("Mudanca de estado da cenoura " + this.getId() + ": " + this.getState());
    }

    private void throw_seeds_and_die () {
        saída = new Message(TypeFunction.hasSeeds, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
        try {
            objout.writeObject((Object)saída);
            entrada = (Response)objin.readObject();
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> throw_seeds_and_die () --> TypeFunction.hasSeeds");
            System.exit(-1);
        }
        catch(ClassNotFoundException ex) {
            System.exit(1);
        }
        if(entrada.getbValor())
            this.setState(EstadoCenoura.Seed);
        else
            this.setState(EstadoCenoura.Dead);
        saída = new Message(TypeFunction.informCenoura, 0, this.getId(), 0, this.getX(), this.getY(), 0, 0, null, EstadoCenoura.Dead);
        try {
            objout.writeObject((Object)saída);
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> germinating () --> throw_seeds_and_die () --> TypeFunction.informCenoura");
            System.exit(-1);
        }
        super.log.Log("Mudanca de estado da cenoura " + this.getId() + ": " + this.getState());
    }

    public void run () {
        
        Random gerador = new Random(this.getId() + 10);
        int flowerTime = 0;
        int numSeeds = 0;

        try {
            objout = new ObjectOutputStream(com.getOutputStream());
            objin = new ObjectInputStream(com.getInputStream());
        }
        catch(IOException ex) {
            ex.printStackTrace();
            System.out.println("Cenoura "+this.getId()+" - Foi impossível criar streams da cenoura " + this.getId() + ".");
            System.out.println(ex.getMessage());
            System.exit(1);
        }
        //State Machine
        while(this.getState() != EstadoCenoura.Terminated)
        {
            switch(this.getState())
            {
                case Seed:
                    this.germinating();
                    break;
                case Growing:
                    saída = new Message(TypeFunction.hasCarrot, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
                    
                    try {
                        objout.writeObject((Object)saída);
                        entrada = (Response)objin.readObject();
                    }
                    catch(IOException ex) {
                        System.out.println("Cenoura "+this.getId()+" - Falha --> != EstadoCenoura.Terminated --> case Growing");
                        System.exit(-1);
                    }
                    catch(ClassNotFoundException ex) {
                        System.exit(1);
                    }
                    if(entrada.getbValor()) {
                        if(this.getEnergia()<this.getEf())
                            this.growing_a_bit ();
                        else {
                            this.flowering();
                            flowerTime = this.getTf();
                        }
                    }
                    else
                        this.die();
                    break;
                case Flower:
                    saída = new Message(TypeFunction.hasCarrot, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
                    try {
                        objout.writeObject((Object)saída);
                        entrada = (Response)objin.readObject();
                    }
                    catch(IOException ex) {
                        System.out.println("Cenoura "+this.getId()+" - Falha --> != EstadoCenoura.Terminated --> case Flower");
                        System.exit(-1);
                    }
                    catch(ClassNotFoundException ex) {
                        System.exit(1);
                    }
                    if(entrada.getbValor()) {
                        if(flowerTime <= 0)
                            this.die_by_oldness();
                        else {
                            saída = new Message(TypeFunction.hasPollen, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
                            try {
                                objout.writeObject((Object)saída);
                                entrada = (Response)objin.readObject();
                            }
                            catch(IOException ex) {
                                System.out.println("Cenoura "+this.getId()+" - Falha --> != EstadoCenoura.Terminated --> case Flower --> TypeFunction.hasPollen");
                                System.exit(-1);
                            }
                            catch(ClassNotFoundException ex) {
                                System.exit(1);
                            }
                            if(entrada.getbValor())
                                numSeeds = this.pollinization();
                            else
                            {
                                flowerTime = this.wait_for_pollen(flowerTime);
                                super.log.Log("FlowerTime Cenoura " + this.getId() + ": " + flowerTime);
                            }
                        }
                    }
                    else
                        this.die();
                    break;
                case Seeding:
                    saída = new Message(TypeFunction.hasCarrot, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
                    try {
                        objout.writeObject((Object)saída);
                        entrada = (Response)objin.readObject();
                    }
                    catch(IOException ex) {
                        System.out.println("Cenoura "+this.getId()+" - Falha --> != EstadoCenoura.Terminated --> case Seeding");
                        System.exit(-1);
                    }
                    catch(ClassNotFoundException ex) {
                        System.exit(1);
                    }
                    if(entrada.getbValor()) {
                        if(numSeeds == 0)
                            this.throw_seeds_and_die();
                        else
                            numSeeds = this.seeding(numSeeds);
                    }
                    else
                        this.die();
                    break;
                case Dead:
                    saída = new Message(TypeFunction.hasSeeds, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
                    try {
                        objout.writeObject((Object)saída);
                        entrada = (Response)objin.readObject();
                    }
                    catch(IOException ex) {
                        System.out.println("Cenoura "+this.getId()+" - Falha --> != EstadoCenoura.Terminated --> case Dead");
                        System.exit(-1);
                    }
                    catch(ClassNotFoundException ex) {
                        System.exit(1);
                    }
                    if(entrada.getbValor())
                        this.setState(EstadoCenoura.Seed);
            }
            saída = new Message(TypeFunction.getEndFlag, 0, 0, 0, 0, 0, 0, 0, null, null);
            try {
                objout.writeObject((Object)saída);
                entrada = (Response)objin.readObject();
            }
            catch(IOException ex) {
                System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> TypeFunction.getEndFlag");
                System.exit(-1);
            }
            catch(ClassNotFoundException ex) {
                System.exit(1);
            }
            if(entrada.getbValor()) {
                this.setState(EstadoCenoura.Terminated);
                saída = new Message(TypeFunction.informCenoura, 0, this.getId(), 0, this.getX(), this.getY(), 0, 0, null, EstadoCenoura.Terminated);
                try {
                    objout.writeObject((Object)saída);
                }
                catch(IOException ex) {
                    System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> EstadoCenoura.Terminated");
                    System.exit(-1);
                }
            }
            try {
                Thread.sleep(gerador.nextInt(201) + 900);
            }
            catch(InterruptedException ex) {
                //Report Exception --> print ex.getMessage()
                return;
            }
        }
    }

    //Métodos Auxiliares --> Privados

    /**
     * Função que recebe a posição da semente a plantar e comunica-a ao servidor.
     * @param x - coordenada X da semente
     * @param y - coordenada Y da semente
     */
    private void sendSeed(int x, int y) {
        saída = new Message(TypeFunction.plantSeed, 0, 0, 0, x, y, 0, 0, null, null);
        try {
            objout.writeObject((Object)saída);
        }
        catch(IOException ex) {
            System.out.println("Cenoura "+this.getId()+" - Falha --> Write Object --> TypeFunction.plantSeed");
            System.exit(-1);
        }
    }
}

